﻿using System;
using System.Collections;
using TSU.FAMC.ComputerEngineering.Classes.Enums;

namespace TSU.FAMC.ComputerEngineering.Classes.BooleanMaths.Fundamental
{
    /// <summary>
    /// Ternary vector model in Boolean Mathematics.
    /// </summary>
    public class TernaryVector : BooleanVector
    {
        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="previousVector">Ternary vector to copy values from.</param>
        public TernaryVector(TernaryVector previousVector) 
            : base(previousVector as BooleanVector)
        {
        }

        /// <summary>
        /// Copy constructor.
        /// </summary>
        /// <param name="previousVector">Boolean vector to copy values from.</param>
        public TernaryVector(BooleanVector previousVector)
        {
            // building boolean values for ternary vector
            var data = new BitArray(previousVector.Length*2);
            for (var i = 0; i < previousVector.Length; i++)
            {
                if (previousVector[i] == BooleanValue.True)
                {
                    data[i*2] = true;
                    data[i*2 + 1] = true;
                }
                else
                {
                    data[i * 2] = false;
                    data[i * 2 + 1] = false;
                }
            }

            // setting boolean data
            BitData = data;
        }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public TernaryVector()
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="length">Length of the vector.</param>
        public TernaryVector(int length) 
            : base(length * 2)
        {
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="values">String values '0'/'1'/'-' to convert to ternary bits.</param>
        public TernaryVector(string values)
        {
            var boolValues = new bool[values.Length * 2];
            for (var i = 0; i < values.Length; i++)
            {
                if (values[i] == '0')
                {
                    boolValues[i*2] = false;
                    boolValues[i*2 + 1] = false;
                }
                else if (values[i] == '1')
                {
                    boolValues[i*2] = true;
                    boolValues[i*2 + 1] = true;
                }
                else if (values[i] == '-')
                {
                    boolValues[i*2] = false;
                    boolValues[i*2 + 1] = true;
                }
                else
                {
                    throw new ArgumentException("Invalid string values to parse (only '0'/'1'/'-' chars are acceptable).");
                }
            }

            BitData = new BitArray(boolValues);
        }


        /// <summary>
        /// Length of the ternary vector.
        /// </summary>
        public new int Length
        {
            get { return BitData.Length / 2; }
            set { BitData.Length = value * 2; }
        }

        /// <summary>
        /// Vector's weight (number of 1 bits).
        /// </summary>
        public new int Weight
        {
            get
            {
                var result = 0;
                foreach (BooleanValue value in this)
                {
                    if (value != BooleanValue.True)
                    {
                        continue;
                    }

                    result++;
                }

                return result;
            }
        }

        /// <summary>
        /// Null vector indicator.
        /// </summary>
        public new bool IsNull
        {
            get
            {
                foreach (BooleanValue value in this)
                {
                    if (value != BooleanValue.False)
                    {
                        return false;
                    }
                }
                return true;
            }
        }

        /// <summary>
        /// Indefinite vector indicator.
        /// </summary>
        public bool IsIndefinite
        {
            get
            {
                foreach (BooleanValue value in this)
                {
                    if (value != BooleanValue.Undefined)
                    {
                        return false;
                    }
                }

                return true;
            }
        }

        /// <summary>
        /// Returns number of determined bits.
        /// </summary>
        public int Rank
        {
            get
            {
                var result = 0;
                foreach (BooleanValue value in this)
                {
                    if (value == BooleanValue.Undefined)
                    {
                        continue;
                    }

                    result++;
                }
                return result;
            }
        }

        /// <summary>
        /// Checkes whether vectors are orthogonal.
        /// </summary>
        /// <param name="vector">Vector to compare with.</param>
        /// <returns>True if vectors are orthgonal.</returns>
        public bool IsOrthogonalTo(TernaryVector vector)
        {
            if (Length != vector.Length)
            {
                throw new InvalidOperationException("Vectors aren't compatible: different lengthes.");
            }

            for (var i = 0; i < Length; i++)
            {
                if (this[i] != BooleanValue.Undefined && vector[i] != BooleanValue.Undefined && this[i] != vector[i])
                {
                    return true;
                }
            }

            return false;
        }

        /// <summary>
        /// Boolean vector indexer.
        /// </summary>
        /// <param name="index">Bit index.</param>
        /// <returns>Vector bit by index.</returns>
        public new BooleanValue this[int index]
        {
            get
            {
                try
                {
                    var ternaryIndex = index * 2;
                    var result = BooleanValue.Undefined;
                    if (!BitData[ternaryIndex] && !BitData[ternaryIndex + 1])
                    {
                        result = BooleanValue.False;
                    }
                    else if (BitData[ternaryIndex] && BitData[ternaryIndex + 1])
                    {
                        result = BooleanValue.True;
                    }

                    return result;
                }
                catch (IndexOutOfRangeException)
                {
                    throw new IndexOutOfRangeException("Invalid ternary vector index.");
                }
            }

            set
            {
                try
                {
                    var ternaryIndex = index * 2;
                    if (value == BooleanValue.False)
                    {
                        BitData[ternaryIndex] = false;
                        BitData[ternaryIndex + 1] = false;
                    }
                    else if (value == BooleanValue.True)
                    {
                        BitData[ternaryIndex] = true;
                        BitData[ternaryIndex + 1] = true;
                    }
                    else
                    {
                        BitData[ternaryIndex] = false;
                        BitData[ternaryIndex + 1] = true;
                    }
                }
                catch (IndexOutOfRangeException)
                {
                    throw new IndexOutOfRangeException("Invalid ternary vector index.");
                }
            }
        }

        /// <summary>
        /// Clones ternay vector object.
        /// </summary>
        /// <returns>Object's clone.</returns>
        public new Object Clone()
        {
            var clone = new TernaryVector(this);
            return clone;
        }


        /// <summary>
        /// Returns enumerator.
        /// </summary>
        /// <returns>Vector's enumerator.</returns>
        public new IEnumerator GetEnumerator()
        {
            for (var i = 0; i < Length; i++)
            {
                yield return this[i];
            }
        }

        /// <summary>
        /// Transforms boolean vector to string representation.
        /// </summary>
        /// <returns>String representation.</returns>
        public override string ToString()
        {
            var values = String.Empty;

            foreach (BooleanValue value in this)
            {
                if (value == BooleanValue.True)
                {
                    values += '1';
                }
                else if (value == BooleanValue.False)
                {
                    values += '0';
                }
                else
                {
                    values += '-';
                }
            }

            return values;
        }

        /// <summary>
        /// Implicit conversion from string to TernaryVector.
        /// </summary>
        /// <param name="values">String values of boolean vector.</param>
        /// <returns>TernaryVector constructed from string values.</returns>
        public static implicit operator TernaryVector(string values)
        {
            return new TernaryVector(values);
        }

        /// <summary>
        /// Implicit conversion from TernaryVector object to string.
        /// </summary>
        /// <param name="vector">TernaryVector object.</param>
        /// <returns>String representation of the ternary vector.</returns>
        public static implicit operator string(TernaryVector vector)
        {
            return vector.ToString();
        }

        /// <summary>
        /// Performs logical AND operation with two TernaryVector objects.
        /// </summary>
        /// <param name="v1">First vector.</param>
        /// <param name="v2">Second vector.</param>
        /// <returns>Operation result.</returns>
        public static TernaryVector operator &(TernaryVector v1, TernaryVector v2)
        {
            if (v1.Length != v2.Length)
            {
                // different lengthes
                throw new ArgumentException("Vectors must have the same length.");
            }

            var values = "";
            for (var i = 0; i < v1.Length; i++)
            {
                if (v1[i] == BooleanValue.True && v2[i] == BooleanValue.True)
                {
                    values += '1';
                }
                else if (v1[i] == BooleanValue.False || v2[i] == BooleanValue.False)
                {
                    values += '0';
                }
                else
                {
                    values += '-';
                }
            }

            return new TernaryVector(values);
        }

        /// <summary>
        /// Performs logical OR operation with two TernaryVector objects.
        /// </summary>
        /// <param name="v1">First vector.</param>
        /// <param name="v2">Second vector.</param>
        /// <returns>Operation result.</returns>
        public static TernaryVector operator |(TernaryVector v1, TernaryVector v2)
        {
            if (v1.Length != v2.Length)
            {
                // different lengthes
                throw new ArgumentException("Vectors must have the same length.");
            }

            var values = "";
            for (var i = 0; i < v1.Length; i++)
            {
                if (v1[i] == BooleanValue.True || v2[i] == BooleanValue.True)
                {
                    values += '1';
                }
                else if (v1[i] == BooleanValue.Undefined || v2[i] == BooleanValue.Undefined)
                {
                    values += '-';
                }
                else
                {
                    values += '0';
                }
            }

            return new TernaryVector(values);
        }

        /// <summary>
        /// Performs logical XOR operation with two TernaryVector objects.
        /// </summary>
        /// <param name="v1">First vector.</param>
        /// <param name="v2">Second vector.</param>
        /// <returns>Operation result.</returns>
        public static TernaryVector operator ^(TernaryVector v1, TernaryVector v2)
        {
            if (v1.Length != v2.Length)
            {
                // different lengthes
                throw new ArgumentException("Vectors must have the same length.");
            }

            var values = "";
            for (var i = 0; i < v1.Length; i++)
            {
                if (v1[i] == BooleanValue.Undefined || v2[i] == BooleanValue.Undefined)
                {
                    values += '-';
                }
                else if (v1[i] != v2[i])
                {
                    values += '1';
                }
                else
                {
                    values += '0';
                }
            }

            return new TernaryVector(values);
        }

        /// <summary>
        /// Performs left cycle shift of vector bits.
        /// </summary>
        /// <param name="vector">TernaryVector object.</param>
        /// <param name="shift">Shift length.</param>
        /// <returns>Shifted vector.</returns>
        public static TernaryVector operator <<(TernaryVector vector, int shift)
        {
            var clone = (TernaryVector)vector.Clone();

            // shifting bits
            while (shift > 0)
            {
                var cycledBit = clone[0];
                for (var i = 0; i < clone.Length - 1; i++)
                {
                    clone[i] = clone[i + 1];
                }
                clone[clone.Length - 1] = cycledBit;

                shift--;
            }

            return clone;
        }
        /// <summary>
        /// Performs right cycle shift of vector bits.
        /// </summary>
        /// <param name="vector">TernaryVector object.</param>
        /// <param name="shift">Shift length.</param>
        /// <returns>Shifted vector.</returns>
        public static TernaryVector operator >>(TernaryVector vector, int shift)
        {
            var clone = (TernaryVector)vector.Clone();

            // shifting bits
            while (shift > 0)
            {
                var cycledBit = clone[clone.Length - 1];
                for (var i = clone.Length - 1; i > 0; i--)
                {
                    clone[i] = clone[i - 1];
                }
                clone[0] = cycledBit;

                shift--;
            }

            return clone;
        }

        /// <summary>
        /// Creates vector reverse (first bit will be the last one and so one).
        /// </summary>
        /// <returns>Reversed ternary vector.</returns>
        public new TernaryVector Reverse()
        {
            var clone = (TernaryVector)Clone();
            for (var i = 0; i < clone.Length / 2; i++)
            {
                var tmp = clone[i];
                clone[i] = clone[clone.Length - i - 1];
                clone[clone.Length - i - 1] = tmp;
            }

            return clone;
        }

        /// <summary>
        /// Builds random boolean vector of specified length.
        /// </summary>
        /// <param name="vectorLength">New vector length.</param>
        /// <returns>Randomized vector.</returns>
        public new static TernaryVector Random(int vectorLength)
        {
            var random = new Random();
            var result = new TernaryVector(vectorLength);
            for (var i = 0; i < result.Length; i++)
            {
                var rnd = (int) Math.Round(random.NextDouble()*2);
                result[i] = rnd == 0 ? BooleanValue.False : rnd == 1 ? BooleanValue.True : BooleanValue.Undefined;
            }

            return result;
        }

        /// <summary>
        /// Calculates distance between two ternary vectors in terms of Hamming distance (number of different bits).
        /// </summary>
        /// <param name="vector">TernaryVector object.</param>
        /// <returns>Hamming distance.</returns>
        public int Distance(TernaryVector vector)
        {
            if (Length != vector.Length)
            {
                throw new ArgumentException("Vectors aren't compatible (different lengthes).");
            }
            var distance = 0;
            for (var i = 0; i < Length; i++)
            {
                if (this[i] != BooleanValue.Undefined && 
                    vector[i] != BooleanValue.Undefined && 
                    this[i] != vector[i])
                {
                    distance++;
                }
            }

            return distance;
        }

        /// <summary>
        /// Compares ternary vector with arbitrary object.
        /// </summary>
        /// <param name="obj">Object to compare with.</param>
        /// <returns>0 if objects are equal, less than zero if current object less than another one or greater than zero if current object greater than another one.</returns>
        public new int CompareTo(Object obj)
        {
            if (obj is TernaryVector)
            {
                var v = obj as TernaryVector;

                // comparing boolean vectors
                if (Length != v.Length)
                {
                    throw new ArgumentException("Vectors aren't comparable: different lengthes.");
                }

                if (IsIndefinite || v.IsIndefinite)
                {
                    throw new ArgumentException("Vectors aren't comparable: vectors should be deterministic.");
                }

                for (var i = 0; i < Length; i++)
                {
                    if (this[i] == v[i]) continue;

                    if (this[i] == BooleanValue.False && v[i] == BooleanValue.True)
                    {
                        return -1;
                    }

                    if (this[i] == BooleanValue.True && v[i] == BooleanValue.False)
                    {
                        return 1;    
                    }
                }
            }

            return Int32.MinValue;
        }


        /// <summary>
        /// Returns hash code for TernaryVector object.
        /// </summary>
        /// <returns>Int hash code.</returns>
        public override int GetHashCode()
        {
            if (ReferenceEquals(BitData, null))
            {
                return 0;
            }

            var hashCode = 0;
            for (var i = Length - 1; i >= 0; i--)
            {
                if (this[i] != BooleanValue.True)
                {
                    continue;
                }

                var power = Length - i - 1;
                hashCode += (int)Math.Pow(2, power);
            }

            return hashCode;
        }
    }
}